#ifndef OS_SEMAPHORE_H
#define OS_SEMAPHORE_H

#include <arch/atomic.h>
#include <os/mutexlock.h>
#include <os/timer.h>
#include <os/clock.h>
#include <os/waitqueue.h>
#include <os/debug.h>

typedef struct
{
    atomic_t counter;    // semaphore count atomic value
    mutexlock_t lock;    // protect semaphore resource lock
    wait_queue_t waiter; // waitting process in this semaphore
} semaphore_t;

#define SEMAPHORE_INIT(semap, val)                                                                                    \
    {                                                                                                                 \
        .counter = ATOMIC_INIT(val), .lock = MUTEX_LOCK_INIT((semap).lock), .waiter = WAIT_QUEUE_INIT((semap).waiter) \
    }

#define DEFINE_SEMAPHORE(semapname, value) \
    semaphore_t semapname = SEMAPHORE_INIT(semapname, value)

semaphore_t *SemaphoreAlloc(int val);
int SemaphoreFree(semaphore_t *semap);
void _SemaphoreDown(semaphore_t *semap);
void _SemaphoreUp(semaphore_t *semap);

static inline void SemaphoreInit(semaphore_t *semaphore, int val)
{
    AtomicSet(&semaphore->counter, val);
    MutexlockInit(&semaphore->lock);
    WaitQueueInit(&semaphore->waiter);
}

// semaphore destroy wakeup all task
static inline void SemaphoreDestroy(semaphore_t *semaphore)
{
    MutexlockIsUnLock(&semaphore->lock);
    // if semaphore present counter
    if (AtomicGet(&semaphore->counter) > 0)
    {
        // wakeup all task from sleepping
        WaitQueueWakeupAll(&semaphore->waiter);
    }
    AtomicSet(&semaphore->counter, 0);
}

static inline void SemaphoreDown(semaphore_t *semap)
{
    MutexlockLock(&semap->lock, MUTEX_LOCK_MODE_WAITTING);
    if (AtomicGet(&semap->counter) > 0)
    {
        AtomicDec(&semap->counter);
        MutexlockUnlock(&semap->lock);
    }
    else
    {
        _SemaphoreDown(semap);
    }
}

static inline int SemaphoreTryDown(semaphore_t *semap)
{
    MutexlockLock(&semap->lock, MUTEX_LOCK_MODE_WAITTING);
    if (AtomicGet(&semap->counter) > 0)
    {
        AtomicDec(&semap->counter);
        MutexlockUnlock(&semap->lock);
        return 0;
    }
    MutexlockUnlock(&semap->lock);
    return -1;
}

static inline void SemaphoreUp(semaphore_t *semap)
{
    MutexlockLock(&semap->lock, MUTEX_LOCK_MODE_WAITTING);
    // semaphore counter++
    AtomicInc(&semap->counter);
    // wake task from waiter list
    if (!list_empty(&semap->waiter.wait_list))
    {
        _SemaphoreUp(semap);
    }
    else
    {
        MutexlockUnlock(&semap->lock);
    }
}

static inline int SemaphoreDownTimeOut(semaphore_t *semap, clock_t ticks)
{
    clock_t start = SysGetTicks();
    clock_t end = start;
    clock_t t = ticks;

    if (!ticks)
    {
        SemaphoreDown(semap);
        return 0;
    }

    while (t > 0)
    {
        end = SysGetTicks();
        if (end > start)
        {
            t -= (end - start);
            start = end;
        }
        if (!SemaphoreTryDown(semap))
            return 0;
    }
    return -1;
}

#endif